from tools import *
from models import *
from queue import PriorityQueue
import csv
from strategy import return_top10_cost_performance_server

class Strategy:
    """
    结合了性价比和迁移的策略:11.6亿
    代表一种处理策略的类
    """
    def __init__(self, operation_list:List[List[Operation]], vm_type:Dict[str,Vmtype], server_type:List[Servertype],parameter: parameter):
        self.operation_list = operation_list
        self.vm_type_dict = vm_type
        self.server_type = server_type
        self.server_type_dict = {t.name: t for t in server_type}
        self.server_list: List[ServerInstance] = [] # 存储物理机实例
        self.server_dict: Dict[int, ServerInstance] = dict() # 物理机id -> 物理机实例
        self.vm_dict: Dict[str, Tuple[Vmtype, ServerInstance]] = dict()
        self.preferred_server_type = self.get_static_preferred_server_type(server_type)
        self.sorted_server_type = self.sort_server_type(server_type)
        self.from_list = []
        self.to_list = []
        self.parameter = parameter

    def sort_server_type(self, server_type:List[Servertype]):
        return sorted([(st.memory / st.core, st.hardware_price, st) for st in server_type])

    def get_static_preferred_server_type(self, server_type):
        # 获取一些比较优秀的服务器类型，购买时先从中选择
        start = int(0.40 * len(self.server_type)) # 按性价比排序，根据调参侠的经验，取40%~70%
        end = int(0.70 * len(self.server_type))   #
        return [ type for _,type in sorted([(-st.cost_performance(), st) for st in self.server_type])] [start:end]
        #return return_top10_cost_performance_server(server_type)

    def run(self, output:Output):
        self.result = output
        self.process()

    def least_instance(self, instance_list:List[ServerInstance], n):
        import heapq
        import functools
        class Wrapper:
            def __init__(self, inst):
                self.inst = inst
            def __lt__(self, other):
                return self.inst.total_usage() > other.inst.total_usage()
            def __eq__(self, other):
                return self.inst.total_usage() == other.inst.total_usage()
        q = PriorityQueue()
        for inst in instance_list:
            w = Wrapper(inst)
            if q.qsize() < n:
                q.put(w)
            else:
                q.put(w)
                q.get()

        res = []
        while not q.empty():
            res.append(q.get().inst)

        return res

    def migrate_single(self, origin:ServerInstance, target:ServerInstance, vmid:str,node :str):
        vmtype = self.vm_dict[vmid][0]
        origin.free(vmtype, vmid)
        target.allocate_appoint_node(vmtype, vmid, node)
        self.vm_dict[vmid] = (vmtype, target)

        if vmtype.is_double:
            self.result.migrate(vmid, target.server_id)
        else:
            self.result.migrate_single_node(vmid, target.server_id, target.record[vmid])


    def migrate(self):
        attempted = 0
        attempt_threshold = 1050000
        migrate_cnt = 0
        migrate_threshold = len(self.vm_dict) * 5 // 1000
        start = 0
        end = len(self.server_list) - 1

        while start < end and migrate_cnt < migrate_threshold:
            origin = self.server_list[start]
            target = self.server_list[end]
            vmlist = list(origin.record.keys())
            for vmid in vmlist:
                vmtype = self.vm_dict[vmid][0]
                right = end
                # 迁移一个虚拟机
                attempted += 1
                #target = self.server_list[right]
                target, node = self.find_first_fit_for_vm_migrate(vmtype)
                if(target==origin):
                    continue
                if target is not None:
                    migrate_cnt += 1
                    self.migrate_single(origin, target, vmid, node)
                    break
                # if instance.can_fit(vmtype):
                #     migrate_cnt += 1
                #     self.migrate_single(origin, target, vmid)
                #     break
                if attempted > attempt_threshold or migrate_cnt >= migrate_threshold:
                    break
            start += 1
            if attempted > attempt_threshold or migrate_cnt >= migrate_threshold:
                break



    def memory_and_core(self):
        memory = 0
        core = 0
        for vmtype,instance in self.vm_dict.values():
            memory += vmtype.memory
            core += vmtype.core
        return memory,core

    def save_server_usage_to_file(self, filename):
        import csv
        with open(filename, 'w') as f:
            w = csv.writer(f)
            w.writerow(['A核剩余CPU', 'A核所有CPU',
                        'B核剩余CPU', 'B核所有CPU',
                        'A核剩余内存', 'A核所有内存',
                        'B核剩余内存', 'B核所有内存'])
            for instance in self.server_list_core:
                w.writerow([instance.a_core_remain, instance.type.core // 2,
                            instance.b_core_remain, instance.type.core // 2,
                            instance.a_memory_remain, instance.type.memory // 2,
                            instance.b_memory_remain, instance.type.memory // 2])

    def accounting(self, day):
        """输出一些信息"""
        l.info('DAY %d' % (day))
        #l.info('length of empty list: %d, total instance: %d+%d' % (
        #    len(self.server_list_core), len(self.server_list_core) , len(self.server_list_full)))
        l.info('number of vm: %d' % (len(self.vm_dict)))

    def calculate_resource_per_day(self, operations:List[Operation]):
        """统计一天的资源请求情况"""
        # 只统计增加的，不统计减少的
        day_memory = 0
        day_core = 0
        for operation in operations:
            if operation.mode == Operation.Op.ADD:
                vmtype = self.vm_type_dict[operation.vm_type_id]
                day_memory += vmtype.memory
                day_core += vmtype.core
        self.max_day_core = day_core
        self.max_day_memory = day_memory
        self.day_memory = 0
        self.day_core = 0

    def put_vm(self, vmid, vmtype:Vmtype) -> Tuple[ServerInstance,bool]:
        """放置一个vm
        如果放不下就买
        """
        # 尝试创建一个实例并且放入物理机中
        created = False
        # 尝试寻找能放下的服务器
        instance, node = self.find_first_fit_for_vm(vmtype)

        if instance is None:
            # 找不到能放下的物理机，那就买一台
            instance = self.purchase_server_that_fit_vm(vmtype)
            created = True
            node = instance.allocate(vmtype, vmid)
        else:
            #找到能放下的就放在既定的结点
            node = instance.allocate_appoint_node(vmtype, vmid,node)
        self.vm_dict[vmid] = (vmtype, instance)

        self.day_memory += vmtype.memory
        self.day_core += vmtype.core
        return instance,created

    def process(self):
        """处理数据，保存结果"""
        #migrate_timer = Timer(start=False)
        #process_timer = Timer(start=False)
        for day, operation_today in enumerate(self.operation_list):

            # if day == 400:
            #     self.save_server_usage_to_file('400.csv')

            self.accounting(day+1)
            # 处理一天的数据

            # 迁移操作
            #migrate_timer.resume()
            #self.migrate()
            #migrate_timer.pause()

            # 统计一天的资源情况，购买服务器的策略依赖于这些统计数据
            self.calculate_resource_per_day(operation_today)

            #process_timer.resume()
            op_list: List[Tuple[str, ServerInstance]] = []     # 存储(虚拟机id, 虚拟机所在的物理服务器)
            new_instance = {}    # 统计购买的各种服务器数量 str -> list[ServerInstance]
            # 第一遍扫描产生一个列表
            for operation in operation_today:
                if operation.mode == Operation.Op.ADD:  # 增加虚拟机
                    instance, created = self.put_vm(operation.vmid, self.vm_type_dict[operation.vm_type_id])
                    if created:
                        if instance.type.name in new_instance:
                            new_instance[instance.type.name].append(instance)
                        else:
                            new_instance[instance.type.name] = [instance]
                        self.server_list.insert(0,instance)
                    op_list.append((operation.vmid, instance))
                else:
                    # 删除虚拟机
                    vmtype, instance = self.vm_dict[operation.vmid]
                    instance.free(vmtype, operation.vmid)
                    self.vm_dict.pop(operation.vmid)

            l.info('购买 %d 服务器' % (sum([len(h) for h in new_instance.values()])))

            # 给没有编号的所有新增instance编号
            for name, instance_list in new_instance.items():
                self.result.purchase_server(self.server_type_dict[name], len(instance_list))
                for inst in instance_list:
                    inst.server_id = ServerInstance.next_index()

            # 扫描所有ADD操作的结果, 输出到Output
            for vmid, instance in op_list:
                vmtype = self.vm_dict[vmid][0]
                node = instance.record[vmid]
                if vmtype.is_double:
                    self.result.allocate(instance.server_id)
                else:
                    self.result.allocate_single_node(instance.server_id, node)
            #process_timer.pause()

            l.debug('done processing day %d' % (day+1))
            self.result.start_a_new_day()

        # 所有输出处理结束后，输出最后统计数据
        total_core = 0
        total_memory = 0
        remain_core = 0
        remain_memory = 0
        for instance in self.server_list:
            total_core += instance.type.core
            total_memory += instance.type.memory
            remain_core += instance.a_core_remain + instance.b_core_remain
            remain_memory += instance.a_memory_remain + instance.b_memory_remain
        l.info('total_core: %d, remain_core: %d'% (total_core, remain_core))
        l.info('total_memory: %d, remain_memory: %d'% (total_memory, remain_memory))

        #l.info('migration time: %fs' % (migrate_timer.elapsed()))
        #l.info('process time: %fs' % (process_timer.elapsed()))

    def purchase_server_that_fit_vm(self, vmtype:Vmtype) -> ServerInstance:
        """
        购买策略(一)
        按照每天的剩余请求购买
        统计每天的所有add请求中的资源数: self.max_day_memory, self.max_day_core
        统计当天已处理的add请求的资源数self.day_memory, self.day_core
        计算得出当天尚未处理的请求中的资源数rest_m, rest_c
        购买时按照这个memory和core的比例购买
        在满足这个比例范围的条件下，使硬件价格最低
        """
        target_type = None
        rest_m = self.max_day_memory - self.day_memory
        rest_c = self.max_day_core - self.day_core
        rest_ratio = rest_m / rest_c
        # NOTICE: 硬编码还是计算？
        #rest_ratio = 1.12
        # 0.1 -> 1131
        leftmargin = self.parameter.leftmargin
        rightmargin = self.parameter.rightmargin
        ratio_range = (rest_ratio - leftmargin, rest_ratio + rightmargin)
        target_type2 = None
        delta = 1000000
        cost = 1000000000
        for r,p,s in self.sorted_server_type:
            if r >= ratio_range[0] and r <= ratio_range[1]:
                candidate = s
                inst = ServerInstance(candidate, None)
                if inst.can_fit(vmtype):
                    if candidate.hardware_price < cost:
                        cost = candidate.hardware_price
                        target_type = candidate
            d = abs(r-rest_ratio)
            if d < delta:
                delta = d
                target_type2 = s
            if r > ratio_range[1]:
                break

        # 选钱最少的
        if target_type is None:
            target_type = target_type2

        if target_type is not None:
            inst = ServerInstance(target_type, None)
            if inst.can_fit(vmtype):
                return inst

        # 没找到满足条件的
        return self.fallback_purchase(vmtype)


    def purchase_compact(self, vmtype:Vmtype):
        """
        购买策略(二)
        买能放下vmtype的服务器里面价格最低的
        """
        return self.fallback_purchase(vmtype)

    def fallback_purchase(self, vmtype:Vmtype):
        """
        如果所有购买尝试都不成功，遍历所有型号，找一个能放下的
        """
        for stype in self.server_type:
            inst = ServerInstance(stype, None)
            if inst.can_fit(vmtype):
                instance = ServerInstance.get_instance(stype)
                return instance

    #迁移时寻找最优的服务器
    def find_first_fit_for_vm_migrate(self, vmtype) -> [ServerInstance, str]:
        """分配虚拟机"""
        fit_list = []

        c_threshold = self.parameter.migrate_c_threshold
        m_threshold = self.parameter.migrate_m_threshold

        signal_max_threshold = self.parameter.migrate_signal_max_threshold
        double_max_threshold = self.parameter.migrate_double_max_threshold

        """
        9 100 50 1126217246
        9 80 40  1122307472
        9 60 30  1123475756
        9 50 25  1123392779
        12 50 25 1123108001
        20 50 25 1122739464
        10 80 40 1122623227
        9 70 30  1123031400
        9 120 60 1126130058
        6 80 40  1123890653
        8 80 40  1124014729
        """

        for server_instance in reversed(self.server_list):
            if server_instance.can_fit(vmtype):
                """单双节点部署，选择最适合的情况"""
                if (vmtype.is_double):
                    """记录一些判断条件，太长了"""
                    double_remain_a_core = server_instance.a_core_remain - vmtype.core / 2
                    double_remain_a_memory = server_instance.a_memory_remain - vmtype.memory / 2
                    double_remain_b_core = server_instance.b_core_remain - vmtype.core / 2
                    double_remain_b_memory = server_instance.b_memory_remain - vmtype.memory / 2
                    if (double_remain_a_memory < m_threshold and double_remain_a_core < c_threshold) \
                            or (double_remain_b_memory < m_threshold and double_remain_b_core < c_threshold) \
                            or (
                            double_remain_b_memory > double_max_threshold and double_remain_b_core > double_max_threshold) \
                            or (
                            double_remain_a_memory > double_max_threshold and double_remain_a_core > double_max_threshold):
                        return server_instance, "both"
                else:
                    """记录一些判断条件，太长了"""
                    single_remain_a_core = server_instance.a_core_remain - vmtype.core
                    single_remain_a_memory = server_instance.a_memory_remain - vmtype.memory
                    single_remain_b_core = server_instance.b_core_remain - vmtype.core
                    single_remain_b_memory = server_instance.b_memory_remain - vmtype.memory
                    if single_remain_a_memory <= m_threshold and single_remain_a_memory >= 0 and \
                            single_remain_a_core <= c_threshold and single_remain_a_core >= 0 \
                            or (
                            single_remain_a_core > signal_max_threshold and single_remain_a_memory > signal_max_threshold):
                        return server_instance, "A"
                    elif single_remain_b_memory <= m_threshold and single_remain_b_core <= c_threshold and \
                            single_remain_b_memory >= 0 and single_remain_b_core >= 0 \
                            or (
                            single_remain_b_memory > signal_max_threshold and single_remain_b_core > signal_max_threshold):
                        return server_instance, "B"


        #没有适合的就不迁移了
        return None, ""


    def find_first_fit_for_vm(self, vmtype) -> [ServerInstance,str]:
        """分配虚拟机"""
        # # 遍历物理机实例列表，查找第一个能放下的
        # for server_instance in reversed(self.server_list):
        #     if server_instance.can_fit(vmtype):
        #         return server_instance
        # return None
        #遍历物理机，查找能放下该虚拟机的物理机，找到是个里面资源剩余最少的
        fit_list = []
        c_threshold = self.parameter.put_c_threshold
        m_threshold = self.parameter.put_m_threshold
        """
        c_threshold == m_threshold的条件下，调参效果
        1136582060   8
        1135283457   9
        1136428902   10
        1136521934   11
        1139035274   20
        timeout      25
        1141700996   28
        1142202331   30
        1143183211   35
        1143657361   40
        """
        signal_max_threshold = self.parameter.put_signal_max_threshold
        double_max_threshold = self.parameter.put_double_max_threshold
        """
        125 75 1134514242
        100 50 1133449869
        150 75 1134995175
        200 100 1136457688
        80 40 1133932775
        120 60 1134103736
        """
        if len(self.server_list) == 0: return None, ""
        for server_instance in reversed(self.server_list):
            if server_instance.can_fit(vmtype):
                fit_list.append(server_instance)
        """如果列表是空，就直接返回"""
        if len(fit_list) == 0:
            return None, ""
        """单双节点部署，选择最适合的情况"""
        if(vmtype.is_double):
            for server_instance in fit_list:
                """记录一些判断条件，太长了"""
                double_remain_a_core = server_instance.a_core_remain - vmtype.core / 2
                double_remain_a_memory = server_instance.a_memory_remain - vmtype.memory / 2
                double_remain_b_core = server_instance.b_core_remain - vmtype.core / 2
                double_remain_b_memory = server_instance.b_memory_remain - vmtype.memory / 2
                if (double_remain_a_memory < m_threshold and double_remain_a_core < c_threshold) \
                    or (double_remain_b_memory < m_threshold and double_remain_b_core< c_threshold)\
                    or (double_remain_b_memory > double_max_threshold and double_remain_b_core > double_max_threshold)\
                    or (double_remain_a_memory > double_max_threshold and double_remain_a_core > double_max_threshold):
                    return server_instance,"both"
        else:
            for server_instance in fit_list:
                """记录一些判断条件，太长了"""
                single_remain_a_core = server_instance.a_core_remain - vmtype.core
                single_remain_a_memory = server_instance.a_memory_remain - vmtype.memory
                single_remain_b_core = server_instance.b_core_remain - vmtype.core
                single_remain_b_memory = server_instance.b_memory_remain - vmtype.memory
                if single_remain_a_memory <= m_threshold and single_remain_a_memory >=0 and\
                    single_remain_a_core <= c_threshold and single_remain_a_core >=0\
                    or (single_remain_a_core > signal_max_threshold and single_remain_a_memory >signal_max_threshold):
                    return server_instance, "A"
                elif single_remain_b_memory <= m_threshold and single_remain_b_core <= c_threshold and\
                        single_remain_b_memory >= 0 and single_remain_b_core >= 0\
                        or (single_remain_b_memory >signal_max_threshold and single_remain_b_core > signal_max_threshold):
                    return server_instance, "B"
        """实在没有很适合的物理机就返回列表的第一个满足条件的"""
        if (vmtype.is_double):
            return fit_list[0], "both"
        else:
            if fit_list[0].a_memory_remain - vmtype.memory >=0 and fit_list[0].a_core_remain - vmtype.core >= 0:
                return fit_list[0], "A"
            elif fit_list[0].b_memory_remain - vmtype.memory >=0 and fit_list[0].b_core_remain - vmtype.core >= 0:
                return fit_list[0], "B"
            else:
                l.fatal('error: %s' % (fit_list))
                exit(-1)


    def belong_full(self, instance:ServerInstance):
        return instance.remain_core_ratio() < 0.2 and instance.remain_memory_ratio() < 0.2
